package no.ffi.gismoidm.servicestubs;
import java.io.File;
import java.io.FileInputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.security.auth.x500.X500Principal;
import no.ffi.gismoidm.certstore.CertificateFileOperations;
import no.ffi.gismoidm.certstore.CertificateService;
import no.ffi.gismoidm.certstore.CertificateServiceFactory;
import no.ffi.gismoidm.idp.Config;
import no.ffi.tpm.crypto.CryptoOperations;
import no.ffi.tpm.crypto.EncryptedObject;
import no.ffi.tpm.crypto.JCECryptoOperations;
import no.ffi.tpm.aik.AIKRequest;

/**
 *
 * @author anders
 */
public class CaServiceHost {
    
    CertificateService certService;
    JCECryptoOperations crOp;
    
    public CaServiceHost() {
        // Find own certificate and private key
        try {
            Config.loadPropertiesFrom(null);
            crOp = new JCECryptoOperations("CA_");
            certService = CertificateServiceFactory.getInstance("HASHNAMES",crOp.getTrustAnchorCert());
        } catch (Exception e) { e.printStackTrace(); }
    }
    
    // Strictly speaking the CA should not validate, only retrieve
    public byte[] getValidatedCertificate(String name) {
        X500Principal principal = new X500Principal(name);
        try {
            System.out.println("Retrieve and validate cert");
            X509Certificate cert = certService.retrieveAndValidate(principal);
            return cert.getEncoded();
        } catch (Exception e) { return null; }
    }
    
    public byte[] getValidatedCertificate(String hash, String serialnumber) {
        try {
            X509Certificate cert = certService.retrieveAndValidate(hash, serialnumber);
            return cert.getEncoded();
        } catch (Exception e) { return null; }
    }
    
    
    public byte[] getPKCS12file(String name) {
        X500Principal principal = new X500Principal(name);
        try {
            String keyfilename = ""; //TODO HashnameStorageService.findDNfilename(principal) + ".p12";
            File f = new File(keyfilename);
            long l = f.length();
            byte[] buffer = new byte[(int)l];
            FileInputStream fis = new FileInputStream(keyfilename);
            fis.read(buffer);
            return buffer;
        } catch (Exception e) { return null; }
    }
    
    public Serializable processRequest(Object in) {
        if (!(in instanceof EncryptedObject)) return new Exception("Unknown request object");
        try {
            AIKRequest aikReq=(AIKRequest) CryptoOperations.deserialize(
                    (byte[])crOp.decrypt( (EncryptedObject)in ));
                return certifyAIK(aikReq);
            } catch (Exception e) { return e; }
    }
    
    public Serializable certifyAIK(AIKRequest aikRequest) {
        X509Certificate aikCert,ekCert;
        EncryptedObject toTpm=null;
        PublicKey aik;
        ekCert = aikRequest.getEC();
        aik = aikRequest.getAIK();
        byte[] pcrHash = aikRequest.getPCR().getPCRHash();
        System.out.println("AIKCert: PCR Hash Values are " + CryptoOperations.toHexString(pcrHash));
        BigInteger serialNo = new BigInteger(pcrHash);
        RSAPublicKey rsaAik = (RSAPublicKey)aik;
        String hname = "tpmnode" + ekCert.getSerialNumber().toString(16);
        String dname = Config.getProperty("CA_DNPREFIX","C=NO, O=FFI, CN=")+hname;
        String san   = hname + Config.getProperty("CA_SANSUFFIX",".ffi.no");
        String certDirectory = Config.getProperty("CA_CERTDIR");
        try {
            if (validateEndorsementKey(ekCert)) {
                X509Certificate caCert = crOp.getTrustAnchorCert();
                PrivateKey caKey = crOp.getMyPrivKey();
                aikCert = CertificateFileOperations.generateCertificate(dname,aik,san,serialNo, caCert,caKey,365*20);
                // For security reasons, the AIK cert should be represented by a random hash value
                String subjectHash = new BigInteger(32,new Random()).toString(16);
                String response = subjectHash + ":"  + aikCert.getSerialNumber().toString();
                System.out.println(response);
                // Save cert in PKI repository
                CertificateFileOperations.saveCertificate(aikCert,certDirectory,subjectHash);
                KeyFactory kf = KeyFactory.getInstance("rsa");
                X509EncodedKeySpec ks = new X509EncodedKeySpec((ekCert.getPublicKey().getEncoded()));
                PublicKey pk = kf.generatePublic(ks);
                return createAIKcertificateResponse(response, aikRequest, pk);
            } else
                return new Exception("Endorsement certificate not valid");
        } catch (Exception e) { e.printStackTrace(); }
        return null;
    }
    
    private Serializable createAIKcertificateResponse(String response,AIKRequest aikRequest,PublicKey pubKey) {
        // The response identifies the AIK through the issuer hash and certificate serial number
        // This string should be encrypted with a symmetric key, and the resulting ciphertext should
        // be encrypted with EK
        try {
            // 1. Make a symmetric key
            SecretKey sk = CryptoOperations.generateSecretKey();
            Cipher cip = Cipher.getInstance(CryptoOperations.symmetricAlg);
            cip.init(Cipher.ENCRYPT_MODE, sk);
            byte[] iv = cip.getIV();
            // 2. Encrypt the response string
            byte[] encrBuff=cip.doFinal(response.getBytes());
            byte[] preResponse = new byte[iv.length+encrBuff.length];
            System.arraycopy(iv,0,preResponse,0,iv.length);
            System.arraycopy(encrBuff,0,preResponse,iv.length,encrBuff.length);
            // 3. Make a TPM-formatted encrypted session key
            byte[] aikRespPlain = aikRequest.buildResponse(sk.getEncoded());
            // 4. And encrypt it with EK
            cip = Cipher.getInstance(CryptoOperations.asymmetricAlgTPM);
            OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA1", "MGF1", new MGF1ParameterSpec("SHA1"), new PSource.PSpecified("TCPA".getBytes("ASCII")));
            cip.init(Cipher.ENCRYPT_MODE, pubKey,oaepSpec);
            byte[] aikRespEnc = cip.doFinal(aikRespPlain);
            EncryptedObject encObj = new EncryptedObject(CryptoOperations.asymmetricAlgTPM,8,preResponse,aikRespEnc,sk);
            return encObj;
        } catch (Exception e) { e.printStackTrace(); }     
        return null;
    }
        
    private boolean validateEndorsementKey(X509Certificate ekCert) {
        try {
            ekCert.checkValidity(); // Check validity period
            X500Principal ekIssuerPrin = ekCert.getIssuerX500Principal();
            // Retrieve and validate ek issuer certificate
            X509Certificate ekIssuerCert = certService.retrieveAndValidate(ekIssuerPrin);
            // Check that signatures bind issuer and subject together correctly
            PublicKey ekIssuerPK = ekIssuerCert.getPublicKey();
            ekCert.verify(ekIssuerPK);
            return true;
        } catch (Exception e) { return false; }
    }
    
    private static PublicKey getEKcertPublicKey(X509Certificate ekCert) throws Exception {
        KeyFactory kf = KeyFactory.getInstance("rsa");
        X509EncodedKeySpec ks = new X509EncodedKeySpec((ekCert.getPublicKey().getEncoded()));
        PublicKey pk = kf.generatePublic(ks);
        return pk;
    }
            

}
